/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.inspur.edp.lcm.metadata.core.index;

import com.inspur.edp.lcm.metadata.api.entity.MetadataPackageForIndex;
import com.inspur.edp.lcm.metadata.api.entity.MetadataProject;
import com.inspur.edp.lcm.metadata.api.entity.ProjectHeader;
import com.inspur.edp.lcm.metadata.api.entity.ProjectMetadataCache;
import com.inspur.edp.lcm.metadata.api.mvnEntity.MavenPackageRefs;
import com.inspur.edp.lcm.metadata.api.service.MdpkgService;
import com.inspur.edp.lcm.metadata.cache.MetadataDevCacheManager;
import com.inspur.edp.lcm.metadata.common.Utils;
import com.inspur.edp.lcm.metadata.core.MetadataProjectCoreService;
import com.inspur.edp.lcm.metadata.core.manager.VersionManager;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ProjectMetadataCacheService {

    MdpkgService mdpkgService = SpringBeanUtils.getBean(MdpkgService.class);
    private final MetadataProjectCoreService projectService = new MetadataProjectCoreService();

    public static ProjectMetadataCacheService getNewInstance(MetadataProject metadataProject, String projectPath,
        String packagePath, String mavenPath) {
        return new ProjectMetadataCacheService(metadataProject, projectPath, packagePath, mavenPath);
    }

    private MetadataProject metadataProject;
    private String projectPath;
    private String packagePath;
    private String mavenPath;
    private String refStrategy;

    private ProjectMetadataCache projectMetadataCache;

    final Map<String, MetadataPackageForIndex> metadataPackageIndexForPackagesHashMap;

    final Map<String, MetadataPackageForIndex> metadataPackageIndexForMavenHashMap;

    private List<String> scannedKeys;

    public ProjectMetadataCacheService(MetadataProject metadataProject, String projectPath, String packagePath,
        String mavenPath) {
        this.metadataProject = metadataProject;
        this.projectPath = projectPath;
        this.packagePath = packagePath;
        this.mavenPath = mavenPath;
        metadataPackageIndexForPackagesHashMap = MetadataPackageIndexServiceForPackages.getInstance(packagePath).getMetadataPackageIndexForPackagesHashMap();
        metadataPackageIndexForMavenHashMap = MetadataPackageIndexServiceForMaven.getInstance(mavenPath).getMetadataPackageIndexForMavenHashMap();
        projectMetadataCache = (ProjectMetadataCache) MetadataDevCacheManager.getProjectMetadataCacheMap(metadataProject.getProjectPath());
    }

    public void syncProjectMetadataCache() {
        if (projectMetadataCache == null) {
            initProjectMetadataCache();
        } else {
            syncCache();
        }
    }

    public void clearCache() {
        projectMetadataCache = null;
    }

    private void initProjectMetadataCache() {
        projectMetadataCache = new ProjectMetadataCache();
        // 获取直接引用的索引元数据包
        getDirectReferences();

        // 获取所有引用的索引原数据包，取版本最新
        getAllReferences();

        // 获取所有元数据路径信息
        getMetadataPackageLocations();

        MetadataDevCacheManager.setProjectMetadataCacheMap(metadataProject.getProjectPath(), projectMetadataCache);
    }

    private void syncCache() {
        Map<String, MetadataPackageForIndex> oldDirectReferences = new HashMap<>(projectMetadataCache.getDirectReferences());
        List<String> oldKeys = new ArrayList<>(oldDirectReferences.keySet());
        getDirectReferences();
        Map<String, MetadataPackageForIndex> currentDirectReferences = projectMetadataCache.getDirectReferences();
        List<String> currentKeys = new ArrayList<>(currentDirectReferences.keySet());
        // 交集
        List<String> retainKeys = new ArrayList<>(oldKeys);
        retainKeys.retainAll(currentKeys);

        // 删除
        oldKeys.removeAll(retainKeys);
        if (oldKeys.size() > 0) {
            initProjectMetadataCache();
            return;
        }

        // 修改
        for (String key : retainKeys) {
            if (!currentDirectReferences.get(key).getLastModified().equals(oldDirectReferences.get(key).getLastModified())) {
                initProjectMetadataCache();
                return;
            }
        }

        // 新增
        currentKeys.removeAll(retainKeys);
        if (currentKeys.size() > 0) {
            scannedKeys = new ArrayList<>();
            currentKeys.forEach(key -> {
                getReferencesRecursive(projectMetadataCache.getAllReferences(), key, currentDirectReferences.get(key));
            });
            getMetadataPackageLocations();
            MetadataDevCacheManager.setProjectMetadataCacheMap(metadataProject.getProjectPath(), projectMetadataCache);
        }
    }

    private void getDirectReferences() {
        Map<String, MetadataPackageForIndex> directReferences = getReferencesFromMetadataProject(metadataProject);
        projectMetadataCache.setDirectReferences(directReferences);
    }

    private Map<String, MetadataPackageForIndex> getReferencesFromMetadataProject(MetadataProject metadataProject) {
        Map<String, MetadataPackageForIndex> references = new HashMap<>();
        List<MavenPackageRefs> existMavenPackageRefs = new ArrayList<>();

        metadataProject.getMetadataPackageRefs().forEach(metadataPackageHeader -> {
            String metadataPackageName = metadataPackageHeader.getName() + Utils.getMetadataPackageExtension();
            final MavenPackageRefs mavenPackageRef = mdpkgService.getGAByMdpkgName(metadataPackageName);
            String mavenIndexKey = null;

            // bo间引用
            if (metadataProject.getProjectRefs() != null && metadataProject.getProjectRefs().size() > 0) {
                ProjectHeader projectHeader = metadataProject.getProjectRefs().stream().filter(
                    header -> header.getName().equals(metadataPackageHeader.getName())).findFirst().orElse(null);
                if (projectHeader != null) {
                    String refProjectPath = projectPath + File.separator + projectHeader.getProjectPath();
                    if (new File(refProjectPath).exists()) {
                        existMavenPackageRefs.add(mavenPackageRef);
                        mavenIndexKey = metadataPackageName + "_" + projectHeader.getProjectPath();
                        MetadataPackageForIndex metadataPackageForIndex = new MetadataPackageForIndex();
                        metadataPackageForIndex.setLocation(refProjectPath);
                        metadataPackageForIndex.setSourceName("bo");
                        String refMdprojPath = projectService.getMdprojPath(refProjectPath);
                        metadataPackageForIndex.setDepFileLocation(refMdprojPath);
                        metadataPackageForIndex.setLastModified(new File(refMdprojPath).lastModified());
                        references.put(mavenIndexKey, metadataPackageForIndex);
                        return;
                    }
                }
            }

            // maven引用
            if (metadataProject.getMavenPackageRefs() != null) {
                for (MavenPackageRefs mavenRef : metadataProject.getMavenPackageRefs()) {
                    if (mavenRef.getGroupId().equals(mavenPackageRef.getGroupId()) && mavenRef.getArtifactId().equals(mavenPackageRef.getArtifactId())) {
                        mavenIndexKey = metadataPackageName + "_" + mavenRef.getGroupId() + "-" + mavenRef.getArtifactId() + "-" + mavenRef.getVersion();
                        existMavenPackageRefs.add(mavenPackageRef);
                        if (!metadataPackageIndexForMavenHashMap.containsKey(mavenIndexKey)) {
                            MetadataPackageIndexServiceForMaven.getInstance(mavenPath).refreshMetadataPackageIndex();
                        }
                        if (metadataPackageIndexForMavenHashMap.containsKey(mavenIndexKey)) {
                            references.put(mavenIndexKey, metadataPackageIndexForMavenHashMap.get(mavenIndexKey));
                            return;
                        }
                        break;
                    }
                }
            }

            // packages引用
            String key = metadataPackageName + "_" + metadataPackageName;
            if (!metadataPackageIndexForPackagesHashMap.containsKey(key)) {
                MetadataPackageIndexServiceForPackages.getInstance(packagePath).refreshMetadataPackageIndex();
            }
            if (metadataPackageIndexForPackagesHashMap.containsKey(key)) {
                references.put(key, metadataPackageIndexForPackagesHashMap.get(key));
                return;
            }
//            "找不到对元数据包" + metadataPackageName + "的引用。"
        });

        // 存在多余的maven引用
        if (metadataProject.getMavenPackageRefs() != null) {
            metadataProject.getMavenPackageRefs().forEach(mavenPackageRef -> {
                Boolean existFlag = existMavenPackageRefs.stream().anyMatch(ref -> ref.getGroupId().equals(mavenPackageRef.getGroupId()) && ref.getArtifactId().equals(mavenPackageRef.getArtifactId()));
                if (existFlag) {
                    return;
                }
                String gav = mavenPackageRef.getGroupId() + "-" + mavenPackageRef.getArtifactId() + "-" + mavenPackageRef.getVersion();
                final String metadataPackageIndexKey = metadataPackageIndexForMavenHashMap.keySet().stream().filter(key -> key.endsWith(gav)).findFirst().orElse(null);
                if (metadataPackageIndexKey != null && metadataPackageIndexForMavenHashMap.containsKey(metadataPackageIndexKey)) {
                    references.put(metadataPackageIndexKey, metadataPackageIndexForMavenHashMap.get(metadataPackageIndexKey));
                }
            });
        }

        return references;
    }

    private void getAllReferences() {
        Map<String, MetadataPackageForIndex> allReferences = new HashMap<>();
        Map<String, MetadataPackageForIndex> directReferences = projectMetadataCache.getDirectReferences();
        scannedKeys = new ArrayList<>();
        directReferences.forEach((key, metadataPackageForIndex) -> {
            getReferencesRecursive(allReferences, key, metadataPackageForIndex);
        });
        projectMetadataCache.setAllReferences(allReferences);
    }

    private void getReferencesRecursive(Map<String, MetadataPackageForIndex> allReferences,
        String metadataPackageForIndexKey, MetadataPackageForIndex ref) {
        if (scannedKeys.contains(metadataPackageForIndexKey)) {
            return;
        } else {
            scannedKeys.add(metadataPackageForIndexKey);
        }
        if (ref == null) {
            return;
        }

        // bo内引用
        if (ref.getSourceName().equals("bo")) {
            String refProjectPath = ref.getLocation();
            final MetadataProject refMetadataProject = projectService.getMetadataProjInfo(refProjectPath);
            final Map<String, MetadataPackageForIndex> depMetadataPackages = getReferencesFromMetadataProject(refMetadataProject);
            ref.setMetadataPackageName(refMetadataProject.getMetadataPackageInfo().getName() + Utils.getMetadataPackageExtension());
            ref.setDepMetadataPackages(depMetadataPackages);
        } else {
            if ("nearest".equals(refStrategy)) {
                // 直接引用版本优先，直接引用版本，并且直接引用版本带具体版本
                String mdpkgName = metadataPackageForIndexKey.split("_")[0];
                // 直接引用中存在对此元数据包的直接引用
                boolean directExistFlag = projectMetadataCache.getDirectReferences().keySet().stream().anyMatch(key -> key.startsWith(mdpkgName));
                // ！直接引用中不包含此包，或者此包不带版本
                boolean directRefFlag = projectMetadataCache.getDirectReferences().containsKey(metadataPackageForIndexKey) && metadataPackageForIndexKey.contains("-");
                if (directExistFlag && !directRefFlag) {
                    return;
                }
                if (!directRefFlag) {
                    // maven和packages下的版本比较
                    String existKey = allReferences.keySet().stream().filter(key -> key.startsWith(ref.getMetadataPackageName())).findFirst().orElse(null);
                    if (existKey != null) {
                        // 比较版本
                        String existVersion = allReferences.get(existKey).getSourceVersion();
                        String newVersion = ref.getSourceVersion();
                        if (existVersion != null && existVersion.equals(newVersion)) {
                            return;
                        }

                        VersionManager versionManager = new VersionManager();
                        String decidedVersion = versionManager.versionCompare(existVersion, newVersion);
                        if (decidedVersion == null || decidedVersion.equals(existVersion)) {
                            return;
                        }

                        allReferences.remove(existKey);
                    }
//                    handleMultiVersion(allReferences, ref);
                }
            } else {
                // maven和packages下的版本比较
                String existKey = allReferences.keySet().stream().filter(key -> key.startsWith(ref.getMetadataPackageName())).findFirst().orElse(null);
                if (existKey != null) {
                    // 比较版本
                    String existVersion = allReferences.get(existKey).getSourceVersion();
                    String newVersion = ref.getSourceVersion();
                    // 如果已经存在的版本和要比较的版本一致，则不再继续处理
                    if (existVersion != null && existVersion.equals(newVersion)) {
                        return;
                    }

                    VersionManager versionManager = new VersionManager();
                    String decidedVersion = versionManager.versionCompare(existVersion, newVersion);
                    // 如果比较出来的版本和已经存在的版本一致，则不再继续处理
                    if (decidedVersion == null || decidedVersion.equals(existVersion)) {
                        return;
                    }

                    allReferences.remove(existKey);
                }
//                handleMultiVersion(allReferences, ref);
            }
        }

        allReferences.put(metadataPackageForIndexKey, ref);

        if (ref.getDepMetadataPackages() == null) {
            return;
        }

        ref.getDepMetadataPackages().forEach((key, metadataPackageForIndex) -> {
            getReferencesRecursive(allReferences, key, metadataPackageForIndex);
        });
    }

    private void handleMultiVersion(Map<String, MetadataPackageForIndex> allReferences, MetadataPackageForIndex ref) {
        // maven和packages下的版本比较
        String existKey = allReferences.keySet().stream().filter(key -> key.startsWith(ref.getMetadataPackageName())).findFirst().orElse(null);
        if (existKey != null) {
            // 比较版本
            String existVersion = allReferences.get(existKey).getSourceVersion();
            String newVersion = ref.getSourceVersion();
            if (existVersion != null && existVersion.equals(newVersion)) {
                return;
            }

            VersionManager versionManager = new VersionManager();
            String decidedVersion = versionManager.versionCompare(existVersion, newVersion);
            if (decidedVersion == null || decidedVersion.equals(existVersion)) {
                return;
            }

            allReferences.remove(existKey);
        }
    }

    private void getMetadataPackageLocations() {
        Map<String, String> metadataPackageLocations = new HashMap<>();
        final Map<String, MetadataPackageForIndex> allReferences = projectMetadataCache.getAllReferences();
        allReferences.values().forEach(ref -> {
            if (ref != null && ref.getMetadataIds() != null) {
                ref.getMetadataIds().forEach(metadataId -> metadataPackageLocations.put(metadataId, ref.getLocation()));
            } else {
//                "ref:" + ref.getLocation()
            }
        });
        projectMetadataCache.setMetadataPackageLocations(metadataPackageLocations);
    }

    public String getRefStrategy() {
        return refStrategy;
    }

    public void setRefStrategy(String refStrategy) {
        this.refStrategy = refStrategy;
    }
}
